#include <string>
#include <jni.h>
#include <android/log.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <random>

#include "hmac/include/hmac.h"
#include "common.h"
#include "hmac/include/md5.h"

using namespace std;


#define HEX_TABLE_LOWERCASE "0123456789abcdef"

inline string hex(const unsigned char *cs, int len) {
    if (cs == nullptr) {
        return "";
    }
    string ret(len * 2, 0x00);

    const char *hex = ENCRYPT_SYSTEM_WORD(HEX_TABLE_LOWERCASE);

    for (size_t i = 0; i < len; ++i) {
        unsigned char v = cs[i];
        ret[2 * i] = hex[(v >> 4) & 0x0F];
        ret[2 * i + 1] = hex[v & 0x0F];
    }

    return ret;
}

#ifdef __cplusplus
extern "C" {
#endif

jboolean init(JNIEnv *, jclass, jobject);

jint hijacked(JNIEnv *env, jclass, jobject context);

jstring signature(JNIEnv *, jclass, jstring, jstring);

// 随机生成 key
void _rnd(unsigned char *, size_t);

struct Hijack {
    // 应用包名
    char *packageName;

    // 应用的签名 获取方式可使用 com.hulytu.android.hijack.Utils.getSignInfoNew 方式获取
    char *signature;

    size_t key_len;
    // 签名密钥
    unsigned char *key;

    void (*rnd)(unsigned char *, size_t);

    Hijack() {
        packageName = ENCRYPT(PACKAGE_NAME);
        signature = ENCRYPT(SIGNATURE);
        key_len = strlen(SIGN_KEY_STR);
        key = new unsigned char[key_len + 1];
        rnd = _rnd;
        (*rnd)(key, key_len);
    }

} *hijack;

jint JNI_OnLoad(JavaVM *vm, void *) {

    JNIEnv *env = nullptr;
    //获取JniEnv
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    //获取一个class对象,对应的java类
    jclass clazz = env->FindClass(ENCRYPT("com/hulytu/android/hijack/AntiHijack"));

    if (clazz == nullptr) {
        return JNI_ERR;
    }

    JNINativeMethod methods[] = {
            // 方法映射
            {ENCRYPT("init"),      ENCRYPT("(Landroid/content/Context;)Z"),                             (jboolean *) init},
            {ENCRYPT("hijacked"),  ENCRYPT("(Landroid/content/Context;)I"),                             (jint *) hijacked},
            {ENCRYPT("signature"), ENCRYPT("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), (jstring *) signature},
    };

    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) != JNI_OK) {
        LOG("JNI_OnLoad fail.");
        return JNI_ERR;
    }

    hijack = new Hijack();

    LOG("JNI_OnLoad: package is %s", hijack->packageName);
    LOG("JNI_OnLoad: signature is %s", hijack->signature);

    return JNI_VERSION_1_6;
}


bool isInit;

jstring getPackageName(JNIEnv *env, jobject context) {
    jmethodID methodID = env->GetMethodID(env->GetObjectClass(context),
                                          ENCRYPT_SYSTEM_WORD("getPackageName"),
                                          ENCRYPT_SYSTEM_WORD("()Ljava/lang/String;"));

    return reinterpret_cast<jstring>(env->CallObjectMethod(context, methodID));
}

bool isValidPackage(JNIEnv *env, jobject context) {

    jstring pName = getPackageName(env, context);

    const char *package = env->GetStringUTFChars(pName, JNI_FALSE);

    if (strcmp(hijack->packageName, package) != 0) {
        env->ReleaseStringUTFChars(pName, package);
        return JNI_FALSE;
    }
    env->ReleaseStringUTFChars(pName, package);

    jclass ctxClazz = env->GetObjectClass(context);

    jmethodID managerId = env->GetMethodID(ctxClazz, ENCRYPT_SYSTEM_WORD("getPackageManager"),
                                           ENCRYPT_SYSTEM_WORD(
                                                   "()Landroid/content/pm/PackageManager;"));

    jobject manager = env->CallObjectMethod(context, managerId);

    jclass managerClazz = env->GetObjectClass(manager);

    jmethodID pInfoId = env->GetMethodID(managerClazz, ENCRYPT_SYSTEM_WORD("getPackageInfo"),
                                         ENCRYPT_SYSTEM_WORD(
                                                 "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"));

    jclass versionClazz = env->FindClass(ENCRYPT_SYSTEM_WORD("android/os/Build$VERSION"));

    jfieldID sdkIntId = env->GetStaticFieldID(versionClazz, ENCRYPT_SYSTEM_WORD("SDK_INT"), "I");
    jint sdkInt = env->GetStaticIntField(versionClazz, sdkIntId);

    jobject pInfo = env->CallObjectMethod(manager, pInfoId, pName, sdkInt >= 28 ? 0x08000000 : 64);

    jclass pInfoClazz = env->GetObjectClass(pInfo);

    jobject signature;

    if (sdkInt >= 28) {

        jfieldID sInfoId = env->GetFieldID(pInfoClazz, ENCRYPT_SYSTEM_WORD("signingInfo"),
                                           ENCRYPT_SYSTEM_WORD("Landroid/content/pm/SigningInfo;"));

        jobject sInfo = env->GetObjectField(pInfo, sInfoId);

        if (sInfo == nullptr) return false;

        jclass sInfoClazz = env->GetObjectClass(sInfo);

        jmethodID signersId = env->GetMethodID(sInfoClazz,
                                               ENCRYPT_SYSTEM_WORD("getApkContentsSigners"),
                                               ENCRYPT_SYSTEM_WORD(
                                                       "()[Landroid/content/pm/Signature;"));

        auto signArray = reinterpret_cast<jobjectArray>(env->CallObjectMethod(sInfo, signersId));

        if (signArray == nullptr || env->GetArrayLength(signArray) == 0) return false;

        signature = env->GetObjectArrayElement(signArray, 0);
    } else {
        // 低于 28 获取方式
        jfieldID signaturesId = env->GetFieldID(pInfoClazz, ENCRYPT_SYSTEM_WORD("signatures"),
                                                ENCRYPT_SYSTEM_WORD(
                                                        "[Landroid/content/pm/Signature;"));

        auto signArray = reinterpret_cast<jobjectArray>(env->GetObjectField(pInfo, signaturesId));
        if (signArray == nullptr || env->GetArrayLength(signArray) == 0) return false;

        signature = env->GetObjectArrayElement(signArray, 0);
    }

    if (signature == nullptr) return false;

    // 转成 byte[]
    jmethodID toByteArrayId = env->GetMethodID(env->GetObjectClass(signature),
                                               ENCRYPT_SYSTEM_WORD("toByteArray"),
                                               ENCRYPT_SYSTEM_WORD("()[B"));

    jobject byteArray = env->CallObjectMethod(signature, toByteArrayId);

    // 获取InputStream对象
    jclass inputStreamClass = env->FindClass(ENCRYPT_SYSTEM_WORD("java/io/ByteArrayInputStream"));

    jmethodID initId = env->GetMethodID(inputStreamClass, ENCRYPT_SYSTEM_WORD("<init>"),
                                        ENCRYPT_SYSTEM_WORD("([B)V"));

    jobject inputStreamObj = env->NewObject(inputStreamClass, initId, byteArray);

    // 获取CertificateFactory对象
    jclass certificateClass = env->FindClass(
            ENCRYPT_SYSTEM_WORD("java/security/cert/CertificateFactory"));

    jmethodID instanceId = env->GetStaticMethodID(certificateClass,
                                                  ENCRYPT_SYSTEM_WORD("getInstance"),
                                                  ENCRYPT_SYSTEM_WORD(
                                                          "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;"));

    jobject factory = env->CallStaticObjectMethod(certificateClass, instanceId,
                                                  env->NewStringUTF(ENCRYPT_SYSTEM_WORD("X509")));

    jmethodID generateCertificateId = env->GetMethodID(
            certificateClass,
            ENCRYPT_SYSTEM_WORD("generateCertificate"),
            ENCRYPT_SYSTEM_WORD("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;")
    );

    jobject certificate = env->CallObjectMethod(factory, generateCertificateId, inputStreamObj);

    jmethodID publicKeyId = env->GetMethodID(env->GetObjectClass(certificate),
                                             ENCRYPT_SYSTEM_WORD("getPublicKey"),
                                             ENCRYPT_SYSTEM_WORD("()Ljava/security/PublicKey;"));

    jobject publicKey = env->CallObjectMethod(certificate, publicKeyId);

    jmethodID toStringId = env->GetMethodID(env->GetObjectClass(publicKey),
                                            ENCRYPT_SYSTEM_WORD("toString"),
                                            ENCRYPT_SYSTEM_WORD("()Ljava/lang/String;"));

    auto str = reinterpret_cast<jstring>(env->CallObjectMethod(publicKey, toStringId));

    // substring
    const char *cstr = env->GetStringUTFChars(str, JNI_FALSE);
    const string s = cstr;

    const string cmpModulus(hijack->signature);

    string modulus = s.substr(s.find(ENCRYPT_SYSTEM_WORD("modulus")) + 8, cmpModulus.length());

    bool isValid = modulus == cmpModulus;

    LOG("isValidPackage: modulus is %d => %s ", isValid, modulus.c_str());

    env->ReleaseStringUTFChars(str, cstr);

    // check
    return isValid;
}

bool isHookPms(JNIEnv *env) {

    jclass clazz = env->FindClass(ENCRYPT_SYSTEM_WORD("android/app/ActivityThread"));

    jfieldID managerId = env->GetStaticFieldID(clazz, ENCRYPT_SYSTEM_WORD("sPackageManager"),
                                               ENCRYPT_SYSTEM_WORD(
                                                       "Landroid/content/pm/IPackageManager;"));

    jobject manager = env->GetStaticObjectField(clazz, managerId);

    jclass proxyClazz = env->FindClass(ENCRYPT_SYSTEM_WORD("java/lang/reflect/Proxy"));

    bool isHook = env->IsInstanceOf(manager, proxyClazz) == JNI_TRUE;

    LOG("isHookPms: %d", isHook);

    return isHook;
}

jboolean init(JNIEnv *env, jclass, jobject context) {

    // 1. 校验PMS hook & 签名
    if (isHookPms(env) || !isValidPackage(env, context)) {
        return JNI_FALSE;
    }

    // 初始化签名 key
    if (!isInit) {
        LOG("init rnd key: %s", hijack->key);
        memcpy(hijack->key, ENCRYPT(SIGN_KEY_STR), hijack->key_len);
        LOG("init key : %d =>  %s", hijack->key_len, hijack->key);
    }

    isInit = true;

    return JNI_TRUE;
}


jint hijacked(JNIEnv *env, jclass, jobject context) {
    // 检测app是否正常运行
    if (isInit && !isHookPms(env) && isValidPackage(env, context)) {
        return JNI_OK;
    }

    hijack->rnd(hijack->key, hijack->key_len);

    return JNI_ERR;
}


jstring signature(JNIEnv *env, jclass, jstring jstr, jstring seed) {

    if (!isInit || jstr == nullptr || seed == nullptr) {
        return env->NewStringUTF("");
    }

    char *ch = const_cast<char *>(env->GetStringUTFChars(jstr, JNI_FALSE));
    const char *timestamp = env->GetStringUTFChars(seed, JNI_FALSE);

    size_t size = strlen(ch) + strlen(timestamp);

    if (size == 0) {
        return env->NewStringUTF("");
    }

    char *destination = strcat(ch, timestamp);

    MD5_CTX context = {0};
    MD5Init(&context);
    MD5Update(&context, (unsigned char *) destination, size);
    unsigned char dest[16] = {0};
    MD5Final(dest, &context);

    env->ReleaseStringUTFChars(jstr, ch);
    env->ReleaseStringUTFChars(jstr, timestamp);

    char szMd5[33] = {0};
    for (unsigned char c : dest) {
        sprintf(szMd5, "%s%02x", szMd5, c);
    }

    return env->NewStringUTF(szMd5);


//    unsigned char hmac[21] = {0};
//
//    hmac_md5(hijack->key, hijack->key_len, (unsigned char *) ch, size, hmac);
//
//    string actualHmac = hex(hmac, 16);
//
//    const char *c_hmac = actualHmac.c_str();
//
//    env->ReleaseStringUTFChars(jstr, ch);
//
//    return env->NewStringUTF(c_hmac);
}

void _rnd(unsigned char *key, size_t len) {

    // 随机出签名 key
    char *meta = ENCRYPT("hijacked9QwErTyUoPsDfGlZxCvBnMqW0E1RtYuI2OpA3S4FgH5J6K7LzXcVbNm8");
    size_t size = strlen(meta);

    char array[len];

    for (int i = 0; i < len; ++i) {
        array[i] = meta[random() % size];
    }

    memcpy(key, array, len);
}

#ifdef __cplusplus
}
#endif
